#-----------------------------------------------------------------||||||||||||--
# Name:         SliderDocument.py
# Purpose:      Main Class For Slider.app
#
# Author:       Jonathan Saggau
#
# Copyright:    (c) 2005 - 2006 Jonathan Saggau
# Licence:      GPL
#-----------------------------------------------------------------||||||||||||--
import sys, types
from Foundation import *
from AppKit import *
from PyObjCTools import NibClassBuilder, AppHelper
#-----------------------------------------------------------------||||||||||||--
SLIDER_GENERIC_ERROR = 1
SLIDER_SHEET_ERROR = 2
SC_IMPORT_ERROR = 3
#-----------------------------------------------------------------||||||||||||--
# TODO: Put Slider.app Version into .SCSlider (plist) file for future versions' backward compat.
# TODO: Prefs validation (in NIB, I hope; I don't really want to validate these in python...)
# TODO: Make a Prefs handling class and yank all prefs stuff out of SliderDocument class
# TODO: Better Icon
#-----------------------------------------------------------------||||||||||||--

class SliderDocument(NibClassBuilder.AutoBaseClass):
   """ Main class of the slider toy for superConductor
       the actual base class is NSDocument
       The following outlets are added to the class:
          activeCheckBox
          currentSliderValueTxtField
          mainWindow
          maxSliderValueTxtField
          minSliderValueTxtField
          onTopCheckBox
          preferencesForm
          preferencesSheet
          slider
          textView """
          
   def _exceptionAlertMessage(self, message = "Error", errorType = SLIDER_GENERIC_ERROR):
     NSBeginAlertSheet(
       'Error',
       'OK', None, None, self.mainWindow, None, None, None,
       0, '%s'%(message,))
            
   def init(self):
      """Initialize the Slider, set properties"""
      self = super(SliderDocument, self).init()  #equiv. to NSDocument.init(self) in this case
      if self is None: return None
      # NSLog("init")
      scOBJ, scServerController = self._importSCStuff()
      self.setProperties(scOBJ.SERVERIP, scOBJ.SERVERPORT, '1000', 'vol', False) # set sane properties
      self.setState(0., 100., 0., "Slider Label", False, {"x":288,"y":210}) # Setup the initial values 
      self.app = NSApp()
      self._active = False # if true, we send commands to SC
      return self

   def _importSCStuff(self):
      """Import the necessary superConductor python classes"""
      try:
         #SuperConductor imports
         from superConductor import scOBJ as scOBJ
         from superConductor.libSC.scServerController import scServerControllerSingleton as scServerController
         return(scOBJ, scServerController)
      except ImportError, errMsg:
         self._exceptionAlertMessage(message = errMsg, errorType = SC_IMPORT_ERROR)

   def windowControllerDidLoadNib_(self, controller):
      """This fires when the gui loads"""
      # NSLog("windowControllerDidLoadNib_")
      self.setState(self.getState()) #Updates the gui on nib load...
      self._putPropertiesOnServer()
      
   def windowDidBecomeKey_(self, theNotification):
      # NSLog("windowDidBecomeKey_")
      try:
         if self.firstTimeTheWindowDidBecomeKey is False: pass
      except AttributeError:
         # NSLog("windowDidBecomeKey_ for the first time")
         # NSLog(theNotification)
         self.setState(self.getState()) #Updates the gui on nib load...
         self._putPropertiesOnServer()
         self.firstTimeTheWindowDidBecomeKey = False
      
   def windowNibName(self):
      """NSDocument requires this method (?)"""
      # NSLog("windowNibName")
      return u'SliderDocument'
      
#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--
# Buttons and other GUI stuff
#-----------------------------------------------------------------||||||||||||--
# windowOnTop

   def isOnTop_(self, sender): # BUTTON
      """Activate / Deactivate sending the slider value, etc. to the server"""
      # NSLog("isOnTop_")
      onTop = sender.intValue()
      if onTop:
         self._onTop()
      else:
         self._notOnTop()

   def getWindowIsOnTop(self):
      """Accessor for the _windowIsOnTop attribute"""
      return bool(self._windowIsOnTop)

   def _onTop(self):
      """Put the windowOnTop"""
      # NSLog("_onTop")
      self.setWindowIsOnTop(True)
      self._updateProperties()

   def _notOnTop(self):
      """Put the window at a normal level (Z)"""
      # NSLog("_notOnTop")
      self.setWindowIsOnTop(False)
      self._updateProperties()

   def setWindowIsOnTop(self, setOnTopTo = None):
      """>>> from Foundation import *
         >>> from AppKit import *
         >>> NSNormalWindowLevel
         0
         >>> NSScreenSaverWindowLevel
         1000
         >>> NSStatusWindowLevel
         25
         >>> NSFloatingWindowLevel
         3 """
      # NSLog("putWindowLevelOnGUI")
      try:
         currentOnTopLevel = self.getWindowIsOnTop()
      except AttributeError:
         # there is no onTopLevel on startup
         currentOnTopLevel = "Not Yet Set"
      if setOnTopTo == None:
         self.setWindowIsOnTop(currentOnTopLevel)
         return
      if currentOnTopLevel is not setOnTopTo:
         self._windowIsOnTop = setOnTopTo
         if setOnTopTo is True:
            self.mainWindow.setLevel_(NSFloatingWindowLevel)
         else:
            self.mainWindow.setLevel_(NSNormalWindowLevel)
            
#-----------------------------------------------------------------||||||||||||--
# Active
   def isActive_(self, sender): # BUTTON
      """Activate / Deactivate sending the slider value, etc. to the server"""
      # NSLog("isActive_")
      on = sender.intValue()
      if on:
         self._activate()
      else:
         self._deactivate()

   def _activate(self):
      """Helper method for isActive_ Button to activate the class"""
      # NSLog(self); NSLog("_activate")
      # NSLog("self.activeCheckBox.intValue() = %s" %self.activeCheckBox.intValue())
      self._active = True
      self._sendSliderValue()

   def _deactivate(self):
      """Helper method for isActive_ Button to deactivate the class"""
      # NSLog(self); NSLog("_deactivate")
      # NSLog("self.activeCheckBox.intValue() = %s" %self.activeCheckBox.intValue())
      self._active = False

#-----------------------------------------------------------------||||||||||||--
# Slider

   def sliderLabel_(self, sender): # Label Text Box
     """Set the slider Label"""
     # NSLog("sliderLabel_")
     label = sender.stringValue()
     # NSLog("Label is %s" %label)

   def sliderMax_(self, sender): # Slider Max Text Box
     """Set the slider maximum value and reset slider position to reflect it"""
     # NSLog("sliderMax_")
     value = sender.floatValue()
     self.slider.setMaxValue_(value)
     self.currentSliderValueTxtField.setFloatValue_(self.slider.floatValue())
     if value < self.minSliderValueTxtField.floatValue():
        self.minSliderValueTxtField.setFloatValue_(value)
        self.slider.setMinValue_(value)
     
   def sliderMin_(self, sender): # Slider Min Text Box
     """Set the slider minimum value and reset slider position to reflect it"""
     # NSLog("sliderMin_")
     value = sender.floatValue()
     self.slider.setMinValue_(value)
     self.currentSliderValueTxtField.setFloatValue_(self.slider.floatValue())
     if value > self.maxSliderValueTxtField.floatValue():
        self.maxSliderValueTxtField.setFloatValue_(value)
        self.slider.setMaxValue_(value)

   def sliderValue_(self, sender): # The current value of the slider
      """Send this value as an OSC message if we're active 
         and set the current value of the text box"""
      # NSLog("sliderValue_")
      theSlider = sender
      self._sendSliderValue()
      self.currentSliderValueTxtField.setFloatValue_(theSlider.floatValue())

   def _sendSliderValue(self):
      """Send the OSC Message"""
      if self._active:
         currentPortIPList = self.server.portIPList
         properties = self.getProperties()

         synthNodeNumber = int(properties[u'SynthNode Number'])
         synthVariableName = str(properties[u'Variable Name'])
         serverIP = str(properties[u'SCServer IP'])
         serverPort = int(properties[u'Port Number'])
         command = ['/n_set', synthNodeNumber, synthVariableName, self.slider.floatValue()]
         # NSLog("Command = %s" %command)
         self.getServer().sendMsg(command, serverIP, serverPort)

#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--
# Properties / state
#-----------------------------------------------------------------||||||||||||--
# Buttons and actions

   def setup_(self, sender): # BUTTON
      """Open the preferencesSheet and set properties."""
      # NSLog("setup_")
      self._rebuildPrefsForm()
      try:
         self.app.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
         self.preferencesSheet,
         self.mainWindow,
         self, None, 0)
      except Exception, errMsg:
         self._exceptionAlertMessage(message = errMsg, errorType = SC_SHEET_ERROR)

   def closeSetup_(self, sender): # BUTTON
      """close the preferencesSheet"""
      # NSLog("closeSetup_")
      self._updateProperties()
      self.preferencesSheet.close()
      NSApplication.sharedApplication().endSheet_(self.preferencesSheet)
      if self._active:
         self._sendSliderValue()

   def _updateProperties(self):
      """
      Helper method for closeSetup_ button that sets everything up
      If the IP or PORT settings are different:
            Rebuild server with new settings
         else
            Change other attributes accordingly
      """
      # NSLog("_updateProperties")
      oldProperties = self.getProperties()        
      newProperties = self._getPropertiesFromPrefsForm()
      if not self._propertiesAreEqual(newProperties, oldProperties):
         self.setProperties(newProperties)
         
#-----------------------------------------------------------------||||||||||||--
# Properties / state methods

   def setProperties(self, *args):
      """Set properties in DB; (All properties are currently strings that are 
         converted to the proper type when passed to the server)

         args are either (serverIP, serverPort, nodeNumber, variableName)
         or the appropriate properties dictionary (like so)
         {
           u'SCServer IP'         :  serverIP,
           u'Port Number'         :  serverPort,
           u'SynthNode Number'    :  nodeNumber,
           u'Variable Name'       :  variableName,
         }
      """
      # NSLog("setProperties")
      dictType = type({'asdf':"dictionary"})
      if type(args[0]) is dictType:
         self._properties = args[0]
      else:
         # all this assumes we have the proper kind of input... user beware     
         # there aren't validators in the GUI yet
         self._properties = {
                   u'SCServer IP'         :  str(args[0]),
                   u'Port Number'         :  str(args[1]),
                   u'SynthNode Number'    :  str(args[2]),
                   u'Variable Name'       :  str(args[3]),
                 }
      try:
         self._putPropertiesOnServer()
      except AttributeError:
         # NSLog("This Message should happen on startup - There is no server yet")
         pass
         
   def getProperties(self):
      """get properties in DB; (All properties are currently strings that are 
         converted to the proper type when passed to the server)"""
      # NSLog("getProperties")
      return(self._properties)
      
   def setState(self, *args):
      """Set state;
         args are either (currentSliderValue, maxSliderValue, minSliderValue, label)
         or the appropriate state dictionary (like so)
         {
           'currentSliderValue'  :  currentSliderValue,
           'maxSliderValue'      :  maxSliderValue,
           'minSliderValue'      :  minSliderValue,
           'label'               :  label,
           'windowOnTop'         :  windowIsOnTop,
           'windowFrameOrigin'   :  windowFrameOrigin
         }
      """
      # NSLog("setState")
      dictType = type({'asdf':"dictionary"})
      if type(args[0]) is dictType:
         state = args[0]
      else:
         # all this assumes we have the proper kind of input... user beware     
         # there aren't validators in the GUI yet
         state = {
                   'currentSliderValue'  :  float(args[0]),
                   'maxSliderValue'      :  float(args[1]),
                   'minSliderValue'      :  float(args[2]),
                   'label'               :  str(args[3]),
                   'windowOnTop'         :  bool(args[4]),
                   'windowFrameOrigin'   :  args[5]
                 }
      # NSLog("setting state to %s" %state)
      self._state = state
      try:
         self._putStateOnGui()
      except Exception, errMsg:
         # NSLog("Can't set state with no GUI: %s" %errMsg)
         # NSLog("Err in setState: %s" %errMsg)
         pass
         
   def getState(self):
      # NSLog("getState")
      return self._state

   def _getStateFromGui(self):
      """Used to update the self._state attribute and to get current state info"""
      # NSLog("_getStateFromGui")
      try:
         origin = self.mainWindow.frame().origin # default = <Foundation.NSPoint x=288.0 y=210.0>
         originx = origin.x
         originy = origin.y
         state = {
                   'currentSliderValue'  :  self.slider.floatValue(),
                   'maxSliderValue'      :  self.maxSliderValueTxtField.floatValue(),
                   'minSliderValue'      :  self.minSliderValueTxtField.floatValue(),
                   'label'               :  self.textView.stringValue(),
                   'windowOnTop'         :  bool(self.onTopCheckBox.intValue()),
                   'windowFrameOrigin'   :  {"x":origin.x,"y":origin.y}
                  }
         
      except AttributeError, err: # if we can't get to the gui,
         # NSLog("Attribute Error in _getStateFromGui %s" %(err,))
         # NSLog("This usually means that we can't get to the GUI")
         state = self.getState()
      return state
      
   def _setStateFromGui(self):
      # NSLog("_setStateFromGui")
      state = self._getStateFromGui()
      self._state = state
        
   def _putStateOnGui(self):
      """
         Put a state dictionary's info onto the gui
      """
      # NSLog("_putStateOnGui")
      state = self.getState()
      if self._getStateFromGui() is not state:
         self.maxSliderValueTxtField.setFloatValue_(state['maxSliderValue'])
         self.slider.setMaxValue_(state['maxSliderValue'])
         self.minSliderValueTxtField.setFloatValue_(state['minSliderValue'])
         self.slider.setMinValue_(state['minSliderValue'])
         self.textView.setStringValue_(state['label'])
         self.slider.setFloatValue_(state['currentSliderValue'])
         self.currentSliderValueTxtField.setFloatValue_(state['currentSliderValue'])
         self.onTopCheckBox.setIntValue_(int(state['windowOnTop']))
         self.setWindowIsOnTop(state['windowOnTop'])
         
         #Window position
         #TODO: make sure no part of the window gets put off screen
         newOrigin = NSPoint()
         newOrigin.x = state['windowFrameOrigin']['x']
         newOrigin.y = state['windowFrameOrigin']['y']
         # NSLog("newOrigin %s" %newOrigin)
         self.mainWindow.setFrameOrigin_(newOrigin)
         
      self._sendSliderValue() #only fires if active (just in case)
      self.setWindowIsOnTop() #just in case the window level changed
      self.mainWindow.display()


   def _rebuildPrefsForm(self):
      """Removes all cells and dumps the current key/value pairs in the preferences
      Database into the window (!)"""
      # NSLog("_rebuildPrefsForm")
      currentProperties = self.getProperties()
      rows, columns = self.preferencesForm.getNumberOfRows_columns_()
      for count in range(rows): # Remove all Cells
         self.preferencesForm.removeEntryAtIndex_(0)

      countIndex = 0 #HACK ALERT; this screams for an iterator or something less stupid
      for eachKey in currentProperties.keys():
         self.preferencesForm.addEntry_(eachKey)
         self.preferencesForm.cellAtIndex_(countIndex).setStringValue_(str(currentProperties[eachKey]))
         countIndex += 1 #HACK ALERT
      self.preferencesForm.sizeToCells() # resize the matrix visually


   def _getPropertiesFromPrefsForm(self):
      """
      The Prefs form is built on the fly from the properties list, so we can get away
      with using the cell title as the key for the DB (!)"""
      # NSLog("_getPropertiesFromPrefsForm")
      cells = self.preferencesForm.cells()
      cellKeyValues = [[cell.title(), cell.stringValue()] for cell in cells]
      newProperties = {}
      for eachKeyValue in cellKeyValues:
         newProperties[eachKeyValue[0]] = eachKeyValue[1]
      return newProperties

   def _propertiesAreEqual(self, compareThis, toThis):
      """(BOOL) test if properties DB (in total) are equal"""
      # NSLog("_propertiesAreEqual")
      if compareThis == toThis: return True
      if compareThis.keys()  != toThis.keys(): return False #this shouldn't really happen...
      else:
         for eachKey in compareThis.keys():
            if str(compareThis[eachKey]) != str(toThis[eachKey]): 
               return False
      return True
      
   def _putPropertiesOnServer(self):
      """Modify the server obj or command as necessary"""
      # NSLog("_putPropertiesOnServer")
      server = self.getServer()
      currentPortIPList = server.portIPList
      properties = self.getProperties()
      serverIP = str(properties[u'SCServer IP'])
      serverPort = int(properties[u'Port Number'])
      newServerPortIp = [serverPort, serverIP]
      if newServerPortIp not in currentPortIPList:
         serverIP = newServerPortIp[0]
         serverPort = newServerPortIp[1]
         server.addServer(serverPort, serverIP)

#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--
#SC Server Voo Doo
#-----------------------------------------------------------------||||||||||||--

   def getServer(self):
      try:
         server = self.server
      except AttributeError:
         self._makeServer()
         server = self.getServer()
      return server
      
   def _makeServer(self):
      properties = self.getProperties()
      scOBJ, scServerController = self._importSCStuff()
      self.server = scServerController(serverIP = properties[u'SCServer IP'],
                                       serverPort = int(properties[u'Port Number']),
                                       verbose = False, 
                                       printErrors = True, 
                                       inactivityTimeout = 30,
                                       timeout = 1)
#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--
# Loading and Saving
#-----------------------------------------------------------------||||||||||||--

   def dataOfType_error_(self, aType):
      """Makes an NSPropertyListSerialization object out of the properties
      """
      # NSLog("dataOfType_error_")
      self._setStateFromGui()
      from PyObjCTools.Conversion import propertyListFromPythonCollection
      stateAndProps = {"state":self.getState(), "properties":self.getProperties()}
      plist = propertyListFromPythonCollection(stateAndProps)
      serializedPlist, err = NSPropertyListSerialization.dataFromPropertyList_format_errorDescription_(plist,NSPropertyListXMLFormat_v1_0)
      return serializedPlist, err

   def loadDataRepresentation_ofType_(self, repr, type):
      # NSLog("loadDataRepresentation_ofType_")
      try:
         from PyObjCTools.Conversion import pythonCollectionFromPropertyList
         plist = pythonCollectionFromPropertyList(repr)
         stateAndProps = NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_(plist, NSPropertyListImmutable)
         stateAndProps = stateAndProps[0]
         state = stateAndProps['state']
         props = stateAndProps['properties']
         self.setProperties(props["SCServer IP"], props["Port Number"], props["SynthNode Number"], props["Variable Name"])
         self.setState(state["currentSliderValue"], state["maxSliderValue"], state["minSliderValue"], state["label"], state['windowOnTop'], state['windowFrameOrigin'])
         return True
      except Exception, errMsg:
         self._exceptionAlertMessage(message = errMsg, errorType = SLIDER_GENERIC_ERROR)
         return False
         
#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--

   def __del__(self):
      """ Clean up after ourselves """
      #NSLog("__del__")
#      for f in range(10):
#         print
#      pass